#! /usr/bin/env python
#coding=utf-8

from .elf_modules import ElfModule

class ComponentDependency(dict):
	def __init__(self, caller, callee, idx):
		self["caller"] = caller
		self["callee"] = callee
		self["calls"] = []
		self["id"] = idx

	def __str__(self):
		return "(%s[%d] -%d-> %s[%d])" % (self["caller"]["name"], self["caller"]["id"], len(self["calls"]), self["callee"]["name"], self["callee"]["id"])

class Component(dict):
	def __init__(self, elf, idx):
		self["id"] = idx
		self["name"] = elf["componentName"]
		self["subsystem"] = elf["subsystem"]

		self["size"] = 0
		self["text_size"] = 0
		self["data_size"] = 0
		self["bss_size"] = 0

		# modules in this Component
		self["modules"] = []

		# Component dependencies
		self["deps"] = []
		#self["deps_indirect"] = []
		#self["deps_total"] = 0

		self["dependedBy"] = []
		#self["dependedBy_indirect"] = []
		#self["dependedBy_total"] = 0

		# Internal module deps
		self["deps_internal"] = []

		self.add_module(elf)

	def __eq__(self, other):
		if not isinstance(other, Component):
			return NotImplemented

		return self["id"] == other["id"]

	def add_module(self, elf):
		elf["component"] = self
		self["modules"].append(elf)
		self["size"] = self["size"] + elf["size"]

		self["text_size"] = self["text_size"] + elf["text_size"]
		self["data_size"] = self["data_size"] + elf["data_size"]
		self["bss_size"] = self["bss_size"] + elf["bss_size"]

	def _find_dep_by_callee(self, callee):
		for dep in self["deps"]:
			if dep["callee"] == callee:
				return dep
		return None

	def _add_component_dependency(self, callee, depClass, idx):
		dep = depClass(self, callee, idx)

		self["deps"].append(dep)
		callee["dependedBy"].append(dep)

		return dep

	def dependsOn(self, com):
		for dep in self["deps"]:
			if dep["callee"] == com:
				return True
		return False

	def setup_deps(self, all_deps, depClass):
		for elf in self["modules"]:
			for dep in elf["deps"]:
				callee = dep["callee"]
				if callee["component"] == self:
					self["deps_internal"].append(dep)
					continue

				component_dep = self._find_dep_by_callee(callee["component"])
				if not component_dep:
					component_dep = self._add_component_dependency(callee["component"], depClass, len(all_deps) + 1)
					all_deps.append(component_dep)

				component_dep["calls"].append(dep)

	def __str__(self):
		return "%s[%d]: external deps[%d], internal deps[%d], dependedBy[%d]" % (self["name"],
			len(self["modules"]), len(self["deps"]), len(self["deps_internal"]), len(self["dependedBy"]))

class ComponentMgr(object):
	def __init__(self, modulemgr, componentClass=None, depClass=None):
		self._components = []
		self._name_dict = {}
		self._idx = 0

		self._all_deps = []

		if componentClass:
			_componentClass = componentClass
		else:
			_componentClass = Component
		if depClass:
			_depClass = depClass
		else:
			_depClass = ComponentDependency
		self._add_all_modules(modulemgr, _componentClass)
		self._setup_deps_tree(_depClass)

	def get_dep_by_id(self, id):
		if id <= 0:
			return None
		if id > len(self._all_deps):
			return None
		return self._all_deps[id - 1]

	def find_by_name(self, name):
		if name in self._name_dict:
			return self._name_dict[name]
		return None

	def find_by_id(self, id):
		if id < 0:
			return None
		if id > self._idx:
			return None
		return self._components[id-1]

	def __add_module(self, elf, componentClass):
		com = self.find_by_name(elf["componentName"])
		if not com:
			self._idx = self._idx + 1
			com = componentClass(elf, self._idx)
			self._components.append(com)
			self._name_dict[elf["componentName"]] = com
			return
		com.add_module(elf)

	def _add_all_modules(self, modulemgr, componentClass):
		for elf in modulemgr.get_all():
			self.__add_module(elf, componentClass)

	def _setup_deps_tree(self, depClass):
		for com in self._components:
			com.setup_deps(self._all_deps, depClass)

	def __update_indirect_deps_recursive(self, com):
		# Already finished
		if com["_recursiveFinished"]:
			return com["depth"]

		maxDepth = 0
		for item in com["deps"]:
			# update child first
			child = item["callee"]
			depth = self.__update_indirect_deps_recursive(child)
			if depth > maxDepth:
				maxDepth = depth
			for dep in child["deps"]:
				if com.dependsOn(dep["callee"]):
					continue
				if dep["callee"] in com["deps_indirect"]:
					continue
				com["deps_indirect"].append(dep["callee"])
			for dep in child["deps_indirect"]:
				if com.dependsOn(dep):
					continue
				if dep in com["deps_indirect"]:
					continue
				com["deps_indirect"].append(dep)

		if len(com["deps"]) > 0:
			maxDepth = maxDepth + 1

		com["_recursiveFinished"] = True
		com["depth"] = maxDepth

		if maxDepth > self._maxDepth:
			self._maxDepth = maxDepth
		depsTotal = len(com["deps"]) + len(com["deps_indirect"])
		if depsTotal > self._maxTotalDepends:
			self._maxTotalDepends = depsTotal

		com["deps_total"] = depsTotal

		return maxDepth

	def get_all(self, xargs=None):
		return self._components

if __name__ == "__main__":
	import sqlite3
	import elf_modules
	conn = sqlite3.connect("symdb.db")
	cursor = conn.cursor()

	mgr = ComponentMgr(elf_modules.ElfModuleMgr(cursor))
	#mgr.report_component_deps()
